/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include <common.h>
#include <os/irq.h>
#include <os/mm.h>
#include <os/sched.h>
#include <screen.h>
#include <sbi.h>
#include <stdio.h>
#include <os/time.h>
#include <os/syscall.h>
#include <os/futex.h>
#include <pgtable.h>
#include <os/elf.h>
#include <type.h>
#include <os/string.h>
#include <user_programs.h>
//#include <test.h>

#include <csr.h>
extern void test_shell();
extern void ret_from_exception();

extern void __global_pointer$();
list_node_t ready_list;

static void init_pcb_stack(
    ptr_t kernel_stack, ptr_t user_stack, ptr_t entry_point,
    pcb_t *pcb, int argc,char** argv)
{
    regs_context_t *pt_regs =
        (regs_context_t *)(kernel_stack - sizeof(regs_context_t));
        pt_regs->sstatus=2;
        pt_regs->regs[1]=entry_point;
        pt_regs->regs[2]=0xf00010000lu;
        pt_regs->regs[10]=argc;
        pt_regs->regs[11]=argv;
        pt_regs->sepc=entry_point;  
        pt_regs->sstatus=0x00040002; 
    regs_context_t *st_regs =
        (switchto_context_t *)(kernel_stack - sizeof(regs_context_t)-sizeof(switchto_context_t));
        st_regs->regs[0]=&ret_from_exception;
        st_regs->regs[1]=kernel_stack - sizeof(regs_context_t);
        st_regs->regs[2]=0;
        st_regs->regs[3]=0;
        st_regs->regs[4]=0;
        st_regs->regs[5]=0;
        st_regs->regs[6]=0;
        st_regs->regs[7]=0;
        st_regs->regs[8]=0;
        st_regs->regs[9]=0;
        st_regs->regs[10]=0;
        st_regs->regs[11]=0;
        st_regs->regs[12]=0;
        st_regs->regs[13]=0;
    /* TODO: initialization registers
     * note: sp, gp, ra, sepc, sstatus
     * gp should be __global_pointer$
     * To run the task in user mode,
     * you should set corresponding bits of sstatus(SPP, SPIE, etc.).
     */

    // set sp to simulate return from switch_to
    /* TODO: you should prepare a stack, and push some values to
     * simulate a pcb context.
     */
}
int pid_table[100];
uint64_t setup_user_pte(){
    PTE* pte_first_entry=pa2kva(allocPage());//因为allocpage返回的是栈顶地址
    //先复用内核高虚拟地址的映射
    PTE* pte_second_entry=allocPage();
    //clear_pgdir(pte_first_entry);
    //clear_pgdir(pa2kva(pte_second_entry));
    pte_first_entry[0x101]= (((uint64_t)pte_second_entry>>12)<<10) | 0x1;
    uint64_t flag = _PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | _PAGE_ACCESSED | _PAGE_DIRTY;
    for(int i=0;i<512;i++)
        *((PTE *)pa2kva(pte_second_entry)+i)=(((0x1<<9)+i)<<19) | flag; 
    return pte_first_entry;//返回页表入口的虚拟地址
}
static void init_pcb()
{
    
    //pcb[NUM_MAX_TASK]是全局变量，初始化主要是要将pcb的kerne_stack和user_stack计算出来并在内存中分配。除此之外，
    //pcb数组和sched1_tasks和lock_tasks数组对应，任务数组是task_info结构体为元素，每个元素有entry_point和task_type两个域
    //entry_point是非常重要的域，指示进入此进程后指令的开始执行位置。初始化时就是进程代码的初始地址，如果发生过中断就是上次执行地址
    //这个地址会存在kernel栈的ra寄存器偏移量对应地址，调度到此进程后会从内存中将之拷贝到cpu的ra寄存器，然后jr ra跳转到该位置.
	init_list_head(&ready_queue);
	int i;
    for(i=0;i<10000;i++)
        mem_mangement[i]=MEM_FREE;
    for(i=0;i<100;i++)
    	pid_table[i]=-1;//-1表示该进程不存在
    pid_table[process_id]=0;//pid=1对应pcb[0]
    pcb[0].pid=process_id++;
    pcb[0].type=USER_PROCESS;
    pcb[0].status=TASK_READY;
    pcb[0].preempt_count=0;
    
    pcb[0].kernel_sp=pa2kva(allocPage()+PAGE_SIZE);
    
    pcb[0].pte_entry=setup_user_pte();
    alloc_page_helper(USER_STACK_ADDR-PAGE_SIZE,pcb[0].pte_entry);
    pcb[0].user_sp=USER_STACK_ADDR;
    init_pcb_stack(pcb[0].kernel_sp,pcb[0].user_sp,
        (uint64_t)load_elf(_elf___test_test_shell_elf, _length___test_test_shell_elf,
              pcb[0].pte_entry,alloc_page_helper),&pcb[0],0,0);  

    /*在这里说明一下load_elf函数的作用。参数依次为char *表示file content  int 表示file length 之后是页表入口地址（虚拟）
    和映射方式（函数指针)         load_elf返回的是该文件的入口地址即以前一直在用的entry_point
        */        
    list_add_tail(&pcb[0].list,&ready_queue);
    
	current_running=&pid0_pcb;//pid0_pcb不会执行，第一次运行do_scheduler时轮换到pcb[0]
    //mutex_lock->lock.status=UNLOCKED;
    
}
long illegal_syscall(void){
    printk("error:illegal_syscall a7 is not for any funtions we have");
}
static void init_syscall(void)
{
        /*extern long invoke_syscall(long, long, long, long);          
        SYSCALL_SPAWN 0         sys_spawn              -->>
        SYSCALL_EXIT 1          sys_exit
        SYSCALL_SLEEP 2         void sys_sleep         -->> do_sleep
        SYSCALL_KILL 3          sys_kill               -->> do_kill
        SYSCALL_WAITPID 4       sys_waitpid            -->> do_waitpid
        SYSCALL_PS 5            sys_process_show       -->> do_process_show
        SYSCALL_GETPID 6        sys_getpid             -->> do_getpid
        SYSCALL_EXEC 7
        SYSCALL_SHOW_EXEC 8
        SYSCALL_FUTEX_WAIT 10   sys_futex_wait
        SYSCALL_FUTEX_WAKEUP 11 sys_futex_wakeup
        SYSCALL_WRITE 20        sys_write
        SYSCALL_READ 21
        SYSCALL_CURSOR 22       sys_move_cursor
        SYSCALL_REFLUSH 23      sys_reflush
        SYSCALL_SERIAL_READ 24
        SYSCALL_SERIAL_WRITE 25
        SYSCALL_READ_SHELL_BUFF 26
        SYSCALL_SCREEN_CLEAR 27 sys_screen_clear

        SYSCALL_GET_TIMEBASE 30 sys_get_timebase
        SYSCALL_GET_TICK 31     sys_get_tick           -->>get_ticks
        SYSCALL_GET_CHAR 32     sys_get_char
        此处开始是我自己搞得调用
        40 mthread_mutex_init
        41 mthread_mutex_destroy
        42 mthread_mutex_trylock
        43 mthread_mutex_lock
        44 mthread_mutex_unlock
        45 mthread_cond_init
        46 mthread_cond_destroy
        47 mthread_cond_wait
        48 mthread_cond_siginal
        49 mthread_cond_broadcast
        50 mthread_barrier_init
        51 mthread_barrier_wait
        52 mthread_barrier_destroy
        63 sys_backspace
        SYSCALL_SCHEDULER 1     void user_do_scheduler -->> do_scheduler 暂时先不用此函数
        写之前先做一个搬运找到所有的系统调用函数名字和在syscall函数指针数组的位置，只有本次实验要做的*/
   
    int i;
    for(i=0;i<64;i++)
        syscall[i]=&illegal_syscall;
    syscall[SYSCALL_SPAWN]=&do_spawn;
    syscall[SYSCALL_EXIT]=&do_exit;
    syscall[SYSCALL_SLEEP]=&do_sleep;
    syscall[SYSCALL_KILL]=&do_kill;
    syscall[SYSCALL_WAITPID]=&do_waitpid;
    syscall[SYSCALL_PS]=&do_process_show;
    syscall[SYSCALL_GETPID]=&do_getpid;
    syscall[SYSCALL_EXEC]=&do_spawn;
    //syscall[SYSCALL_YIELD]=;//找不到这个函数
    //syscall[1]=&do_scheduler;
    syscall[SYSCALL_FUTEX_WAIT]=&futex_wait;
    syscall[SYSCALL_FUTEX_WAKEUP]=&futex_wakeup;
    syscall[SYSCALL_WRITE]=&screen_write;//这里做个修改，以前是screen_write
    syscall[SYSCALL_CURSOR]=&screen_move_cursor;//以前是screen_move_cursor
    syscall[SYSCALL_REFLUSH]=&screen_reflush;
    syscall[SYSCALL_SCREEN_CLEAR]=&screen_clear;
    syscall[SYSCALL_GET_TIMEBASE]=&get_time_base;
    syscall[SYSCALL_GET_TICK]=&get_ticks;
    syscall[SYSCALL_GET_CHAR]=&sbi_console_getchar;
    //--------------自添加部分-----------------------
    syscall[40]=&do_mutex_lock_init;
    syscall[41]=&do_mutex_lock_destroy;
    syscall[43]=&do_mutex_lock_acquire;
    syscall[44]=&do_mutex_lock_release;
    syscall[63]=&screen_backspace;    
}
void do_process_show(){
    int i=0;
    char *status_table[5]={"TASK_BLOCKED","TASK_RUNNING","TASK_READY",
    "TASK_ZOMBIE""TASK_EXITED"};
    prints("[PROCESS TABLE]\n");
    for(i=0;i<16;i++)
    	if(pcb[i].pid)
    		prints("pcb[%d]pid: %d STATUS: %s \n",i,pcb[i].pid,
        			status_table[pcb[i].status]);
}

pid_t do_spawn(const char * file_name,int argc, char* argv[], spawn_mode_t mode){
	//find target file by matching filename with file_list_struc.direct use funtion given by our text_frame
	int file_length;
	unsigned char* _elf___test_process;
	static int arg_num;
	static char** argv_static;
	argv_static=argv;
	arg_num=argc;
    int id,i;
    //find the target file from elf_file_list by matching name
    for(i=0;i<4;i++){
    	if(kstrcmp(elf_files[i].file_name,file_name)==0)
    		break;
    }
    if(i>=4){
    	prints("error:no such a file with name %s",file_name);
    	return 0;
    }
    file_length=*elf_files[i].file_length;
    _elf___test_process=elf_files[i].file_content;
    //find一个空闲的pcb
    for(i=0;i<16;i++)
        if(!pcb[i].pid)
            break;
    id=i;
    pid_table[process_id]=id;
    pcb[id].pid=process_id++;
    pcb[id].status=TASK_READY;
    pcb[id].type=USER_PROCESS;
    pcb[id].mode=mode;
    pcb[id].preempt_count=0;
    init_list_head(&pcb[id].wait_list);
    pcb[id].kernel_sp=pa2kva(allocPage()+PAGE_SIZE);
    
    pcb[id].pte_entry=setup_user_pte();
    pcb[id].user_sp=USER_STACK_ADDR;
    PTE *user_sp_kva_8=alloc_page_helper(USER_STACK_ADDR-PAGE_SIZE,pcb[id].pte_entry);
    char *user_sp_kva_1=(uint64_t)user_sp_kva_8+arg_num*8;
    //向用户栈底部存储argv对应的所有元素
    int offset=0;
    for(i=0;i<arg_num;i++){
    	user_sp_kva_8[i]=USER_STACK_ADDR-PAGE_SIZE+arg_num*8+offset;
    	for(int j=0;;j++){
    		user_sp_kva_1[offset++]=argv_static[i][j];
    		if(argv[i][j]=='\0')
    			break;
    	}
    }

    //argv指向地址就是栈底的地址(&argv[0])
    argv=(uint64_t)(pcb[id].user_sp-PAGE_SIZE);
    
    init_pcb_stack(pcb[id].kernel_sp,pcb[id].user_sp,
        (uint64_t)load_elf(_elf___test_process, file_length,
              pcb[id].pte_entry,alloc_page_helper),&pcb[id],argc,argv);  
    list_add_tail(&pcb[id].list,&ready_queue);
    return process_id-1;  
}
void do_exit(void){
    list_node_t *cur=current_running->wait_list.next;  
    while(cur!=&current_running->wait_list){
            list_entry(cur,pcb_t,list)->status=TASK_READY;      
            cur=cur->next;
            list_add_tail(cur->prev,&ready_queue);
            }
	
	//自动清理模式要回收栈
		//pid_table[current_running->pid]=-1;
        current_running->pid=0;//判断pcb有没有被进程占用是看pid是否为0 
		current_running->type=TASK_EXITED;
		freePage(current_running->kernel_sp,0);
		freePage(current_running->user_sp,0);
		init_list_head(&current_running->wait_list);
        list_del(&current_running->list);
	 
		do_scheduler();
}
int do_kill(pid_t pid){
	if(pid==1)
		{
			prints("you can not kill shell\n");
			return 0;	
		}
	if(pid_table[pid]<=0||pcb[pid_table[pid]].pid!=pid)//此编号对应进程不存在
	{
        prints("illegal kill\n");
		return 0;//return 0代表失败
	}
    list_node_t *cur=pcb[pid_table[pid]].wait_list.next;  
    while(cur!=&pcb[pid_table[pid]].wait_list){
            list_entry(cur,pcb_t,list)->status=TASK_READY;      
            cur=cur->next;
            list_add_tail(cur->prev,&ready_queue);}
      
	
		pcb[pid_table[pid]].pid=0;//判断pcb有没有被进程占用是看pid是否为0
		list_del(&pcb[pid_table[pid]].list);
        init_list_head(&pcb[pid_table[pid]].wait_list);
		pcb[pid_table[pid]].type=TASK_EXITED;
		freePage(pcb[pid_table[pid]].kernel_sp,0);
		freePage(pcb[pid_table[pid]].user_sp,0);
		pid_table[pid]=-1;
	
	return 1;
}
int do_waitpid(pid_t pid){
	if(pid_table[pid]<0){
		prints("error target not exist");
		return 0;//0代表失败
	}
	   //pcb[pid_table[pid]].wait_pid=current_running->pid;
        list_add_tail(&current_running->list,&pcb[pid_table[pid]].wait_list);
	//list_del(&current_running->list);
	//以下部分从do_scheduler截取
		pcb_t *last_running;
    	last_running=current_running;      
        current_running->status=TASK_BLOCKED;
        current_running=&pcb[pid_table[pid]];
        list_del(&current_running->list);
    	current_running->status=TASK_RUNNING;
    	//screen_reflush();
    	vt100_move_cursor(current_running->cursor_x,
                      current_running->cursor_y);
    	screen_cursor_x = current_running->cursor_x;
    	screen_cursor_y = current_running->cursor_y;
    	switch_to(last_running,current_running);
    	return 1;
}
int do_getpid(){
	return current_running->pid;
}

// jump from bootloader.
// The beginning of everything >_< ~~~~~~~~~~~~~~
int main()
{
    // init Process Control Block (-_-!)
    
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n\r");

    
/*	
    // init futex mechanism
    init_system_futex();
*/
    //init interrupt (^_^)
    init_exception();
    printk("> [INIT] Interrupt processing initialization succeeded.\n\r");

    // init system call table (0_0)
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n\r");

    // fdt_print(riscv_dtb);

    // init screen (QAQ)
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n\r");

    
    // TODO:
    // Setup timer interrupt and enable all interrupt
    enable_interrupt();
    time_base = sbi_read_fdt(TIMEBASE);
     //do_scheduler();
    //sbi_set_timer(get_ticks() + TIMER_INTERVAL);
    reset_irq_timer();
    //printk("reset_irq_timer succeeded\n\r");
    //enable_interrupt();

    while (1) {
        // (QAQQQQQQQQQQQ)
        // If you do non-preemptive scheduling, you need to use it
        // to surrender control do_scheduler();
        //enable_interrupt();
        //user_do_scheduler();
         //__asm__ __volatile__("wfi\n\r":::);
     //
    }
    return 0;
}
